package megrez.oss.minio.web;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;

import org.apache.tika.Tika;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import io.minio.GetObjectResponse;
import io.minio.StatObjectResponse;
import io.minio.http.Method;
import io.minio.messages.Tags;
import megrez.boot.autoconfigure.minio.MinioObjectClient;
import megrez.oss.common.FileEmptyException;
import megrez.oss.common.FileNameCounter;
import megrez.oss.common.FileNameException;
import megrez.oss.common.SecurityUtils;
import megrez.oss.common.Snowflake;
import megrez.oss.minio.utils.Helper;
import megrez.oss.minio.utils.ObjectNotFoundException;
import megrez.oss.minio.utils.UserData;
import megrez.oss.minio.web.vo.ItemVO;
import megrez.oss.minio.web.vo.MetadataVO;
import megrez.oss.minio.web.vo.StatObjectResponseVO;

/**
 * 对象接口
 * 
 * @author Lucky Yang
 * @since 2.6.9
 */
@RestController
@RequestMapping("/v1/{bucket}/objects")
public class ObjectResource {
    @Autowired
    private MinioObjectClient objectClient;

    private static final Tika tika = new Tika();

    /**
     * 上传文件
     * 
     * @param bucket
     * @param file
     * @param reqObjectId 对象ID，如果不指定，自动生成
     * @return 对象ID
     * @throws IOException
     */
    @PostMapping("/upload")
    @ResponseStatus(HttpStatus.CREATED)
    public String put(@PathVariable("bucket") String bucket, @RequestParam("file") MultipartFile file,
            @RequestParam(value = "objectId", required = false) String reqObjectId) throws IOException {

        if (file.isEmpty()) {
            throw new FileEmptyException();
        }

        String fileName = file.getOriginalFilename();
        if (!StringUtils.hasText(fileName)) {
            throw new FileNameException();
        }

        fileName = StringUtils.cleanPath(fileName);
        final String objectId = reqObjectId == null ? String.valueOf(Snowflake.nextId()) : reqObjectId;
        final String contentType = tika.detect(file.getInputStream());
        final String objectName = Helper.objectName(objectId);
        final UserData userData = new UserData(fileName, SecurityUtils.getCurrentUser(), objectId);

        objectClient.putObject(bucket, objectName, file.getInputStream(),
                builder -> builder.contentType(contentType).userMetadata(userData.toMap()));

        return objectId;
    }

    /**
     * 删除对象
     * 
     * @param bucket
     * @param objectId
     */
    @DeleteMapping("/{objectId}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void remove(@PathVariable("bucket") String bucket, @PathVariable("objectId") String objectId) {
        final String objectName = Helper.objectName(objectId);
        existObject(bucket, objectName);
        objectClient.deleteObject(bucket, objectName);
    }

    /**
     * 删除对象标签
     * 
     * @param bucket
     * @param objectId
     */
    @DeleteMapping("/{objectId}/tags")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void removeTags(@PathVariable("bucket") String bucket, @PathVariable("objectId") String objectId) {
        final String objectName = Helper.objectName(objectId);
        existObject(bucket, objectName);
        objectClient.deleteObjectTags(bucket, objectName);
    }

    /**
     * 设置对象标签
     * 
     * @param bucket
     * @param objectId
     */
    @PostMapping("/{objectId}/tags")
    @ResponseStatus(HttpStatus.CREATED)
    public void setTags(@PathVariable("bucket") String bucket, @PathVariable("objectId") String objectId,
            @RequestBody Map<String, String> tags) {
        final String objectName = Helper.objectName(objectId);
        existObject(bucket, objectName);
        objectClient.setObjectTags(bucket, objectName, Tags.newObjectTags(tags));
    }

    /**
     * 获取对象标签
     * 
     * @param bucket
     * @param objectId
     * @return
     */
    @GetMapping("/{objectId}/tags")
    public Map<String, String> getTags(@PathVariable("bucket") String bucket,
            @PathVariable("objectId") String objectId) {
        final String objectName = Helper.objectName(objectId);
        existObject(bucket, objectName);
        return objectClient.getObjectTags(bucket, objectName).get();
    }

    /**
     * 获取单个对象
     * 
     * @param bucket
     * @param objectId
     * @param response
     * @throws IOException
     */
    @GetMapping("/{objectId}/download")
    public void get(@PathVariable("bucket") String bucket, @PathVariable("objectId") String objectId,
            final HttpServletResponse response) throws IOException {
        final String objectName = Helper.objectName(objectId);
        existObject(bucket, objectName);

        // 对象信息
        StatObjectResponse statObjectResponse = objectClient.statObject(bucket, objectName);
        UserData userData = UserData.of(statObjectResponse.userMetadata());
        // 文件数据
        GetObjectResponse objectResponse = objectClient.getObject(bucket, objectName);

        // 设置响应
        response.setContentType(statObjectResponse.contentType());
        response.setHeader("Content-Disposition",
                String.format("inline; filename=\"%s\"", userData.getFileName()));

        FileCopyUtils.copy(objectResponse, response.getOutputStream());
    }

    /**
     * 批量获取多个对象
     * 
     * @param bucket
     * @param objectIds
     * @param response
     * @throws IOException
     */
    @GetMapping("/download")
    public void get(@PathVariable("bucket") String bucket,
            @RequestParam(value = "objectIds") Set<String> objectIds,
            final HttpServletResponse response) throws IOException {

        List<StatObjectResponse> statObjectResponseList = new ArrayList<>();
        for (String objectId : objectIds) {
            String objectName = Helper.objectName(objectId);
            // 对象信息
            statObjectResponseList.add(objectClient.statObject(bucket, objectName));
        }

        byte[] buffer = new byte[2048];
        FileNameCounter fileNameCounter = new FileNameCounter();
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ZipOutputStream zos = new ZipOutputStream(baos)) {

            for (StatObjectResponse statObjectResponse : statObjectResponseList) {
                // 文件数据
                GetObjectResponse objectResponse = objectClient.getObject(bucket, statObjectResponse.object());
                // 避免重复的文件名
                UserData userData = UserData.of(statObjectResponse.userMetadata());
                ZipEntry zipEntry = new ZipEntry(fileNameCounter.count(userData.getFileName()));
                zos.putNextEntry(zipEntry);

                int bytesRead;
                while ((bytesRead = objectResponse.read(buffer)) != -1) {
                    zos.write(buffer, 0, bytesRead);
                }

                zos.closeEntry();
            }

            response.setContentType("application/zip");
            response.addHeader("Pragma", "no-cache");
            response.addHeader("Expires", "0");
            final ServletOutputStream sos = response.getOutputStream();
            sos.write(baos.toByteArray());
            sos.flush();
        }
    }

    /**
     * 获取对象预签名URL
     * 
     * @param bucket
     * @param objectId
     * @param method   请求方法，包括：POST, GET, PUT, DELETE
     * @param expiry   失效时间，默认7天，单位：秒
     * @return
     */
    @GetMapping("/{objectId}/presigned-url")
    public String getPresignedUrl(@PathVariable("bucket") String bucket,
            @PathVariable("objectId") String objectId, @RequestParam("method") Method method,
            @RequestParam(value = "expiry", required = false) Integer expiry) {
        String objectName = Helper.objectName(objectId);
        Duration expiryDuration = expiry == null ? null : Duration.ofSeconds(expiry);

        existObject(bucket, objectName);
        return objectClient.getPresignedObjectUrl(bucket, objectName, method, expiryDuration);
    }

    /**
     * 查询对象
     * 
     * @param bucket
     * @param prefix              对象前缀
     * @param includeUserMetadata 是否包含用户元数据
     * @return
     */
    @GetMapping(value = "/list")
    public List<ItemVO> list(@PathVariable("bucket") String bucket,
            @RequestParam(value = "prefix", required = false) String prefix,
            @RequestParam(value = "includeUserMetadata", required = false) Boolean includeUserMetadata) {
        return objectClient.listObjects(bucket, prefix, includeUserMetadata).stream().map(ItemVO::of)
                .collect(Collectors.toList());
    }

    /**
     * 获取单个对象状态
     * 
     * @param bucket
     * @param objectId
     * @return
     */
    @GetMapping("/{objectId}/stat")
    public StatObjectResponseVO getState(@PathVariable("bucket") String bucket,
            @PathVariable("objectId") String objectId) {
        String objectName = Helper.objectName(objectId);
        existObject(bucket, objectName);

        return StatObjectResponseVO.of(objectClient.statObject(bucket, objectName));
    }

    /**
     * 获取多个对象状态
     * 
     * @param bucket
     * @param objectIds
     * @return
     */
    @GetMapping("/stat")
    public List<StatObjectResponseVO> getState(@PathVariable("bucket") String bucket,
            @RequestParam(value = "objectIds") Set<String> objectIds) {
        List<StatObjectResponseVO> statObjectResponseList = new ArrayList<>();
        for (String objectId : objectIds) {
            statObjectResponseList.add(getState(bucket, objectId));
        }

        return statObjectResponseList;
    }

    /**
     * 获取单个对象元数据
     * 
     * @param bucket
     * @param objectId
     * @return
     */
    @GetMapping("/{objectId}/metadata")
    public MetadataVO getMetadata(@PathVariable("bucket") String bucket,
            @PathVariable("objectId") String objectId) {
        String objectName = Helper.objectName(objectId);
        existObject(bucket, objectName);

        return MetadataVO.of(objectClient.statObject(bucket, objectName));
    }

    /**
     * 获取多个对象元数据
     * 
     * @param bucket
     * @param objectIds
     * @return
     */
    @GetMapping("/metadata")
    public List<MetadataVO> getMetadata(@PathVariable("bucket") String bucket,
            @RequestParam(value = "objectIds") Set<String> objectIds) {
        List<MetadataVO> metadataList = new ArrayList<>();
        for (String objectId : objectIds) {
            String objectName = Helper.objectName(objectId);
            metadataList.add(MetadataVO.of(objectClient.statObject(bucket, objectName)));
        }

        return metadataList;
    }

    private void existObject(String bucket, String objectName) {
        if (objectClient.existObject(bucket, objectName)) {
            return;
        }

        throw new ObjectNotFoundException(objectName);
    }
}
